package net.cyndeline.rldungeon.dgs.strategy.help

import net.cyndeline.rldungeon.dgs.graph.{PointlessAreaData, Room}

/**
 * Represents a vertex in a graph, or multiple merged vertices.
 * @param v A vertex represented by this node.
 * @param vs Additional vertices if this is a super-node.
 */
class CollapsedNode[RepresentedType <: Room[RepresentedType]](v: RepresentedType, vs: RepresentedType*) {
  private var pointlessVertices = Set[RepresentedType]()
  private var verticesThatHoldRewards = Set[RepresentedType]()
  private var verticesThatHoldActivators = Set[RepresentedType]()

  val vertexCollection: Set[RepresentedType] = vs.toSet + v

  checkForPointlessRoomsAndRewardCapacity()

  /**
   * Constructs a node from a set of vertices.
   * @param vertices All vertices represented by this node. Must be non-empty.
   */
  def this(vertices: Set[RepresentedType]) = {
    this({
      if (vertices.isEmpty)
        throw new Error("Attempted to initiate collapsed node with empty vertex set.")
      vertices.head
    }, (vertices - vertices.head).toSeq:_*)
  }

  /**
   * Checks if this node represents at least one pointless vertex.
   * @return True if the vertex collection contains at least one pointless vertex, otherwise false.
   */
  def isPointless: Boolean = !pointlessVertices.isEmpty

  /**
   * Returns the main area connection of one of the vertices represented by this node, or throws
   * an exception if this node isn't pointless or is but doesn't have pointless area data associated
   * with its vertices.
   * @return The main area connection of a vertex represented by this node.
   */
  def mainAreaConnection: RepresentedType = {
     if (!isPointless)
       throw new Error("The collapsed node " + this + " isn't pointless, and can't retrieve main area connections.")

    val pointlessVertices = allPointlessVertices
    val pointlessData = pointlessVertices.head.pointlessAreaData.getOrElse {
      throw new Error("The collapsed node " + this + " contains a pointless vertex " + pointlessVertices.head + ", but it doesn't have any pointless data.")
    }

    pointlessData.mainAreaConnection
  }

  /**
   * Returns the main area connection of one of the vertices represented by this node if one exist. Use this instead
   * of mainAreaConnection when working with nodes that may not be pointless.
   * @return The main area connection of this node if it contains pointless nodes with its data set, otherwise None
   */
  def getMainAreaConnection: Option[RepresentedType] = {
    val pointlessVertices = allPointlessVertices

    if (pointlessVertices.isEmpty)
      None
    else
      Option(pointlessVertices.head.pointlessAreaData.get.mainAreaConnection)
  }

  /**
   * Checks if this node represents at least one vertex that can hold rewards.
   * @return True if the vertex collection contains at least one vertex that can hold rewards, otherwise false.
   */
  def canHoldRewards: Boolean = !verticesThatHoldRewards.isEmpty

  /**
   * Checks if this node represents at least one vertex that can hold activators.
   * @return True if the vertex collection contains at least one vertex that can hold activators, otherwise false.
   */
  def canHoldActivators: Boolean = !verticesThatHoldActivators.isEmpty

  /**
   * @return Every vertex represented by this node that is pointless.
   */
  def allPointlessVertices: Set[RepresentedType] = pointlessVertices

  /**
   * @return Every vertex represented by this node that can hold additional rewards.
   */
  def allVerticesThatCanHoldRewards: Set[RepresentedType] = verticesThatHoldRewards

  /**
   * @return Every vertex represented by this node that can hold additional activators.
   */
  def allVerticesThatCanHolActivators: Set[RepresentedType] = verticesThatHoldActivators
  /**
   * Retrieves a single vertex if this node represents one. Use this whenever a single vertex is to be retrieved,
   * as the method fails fast if more/less than one vertex is present.
   * @return The single vertex represented by this node.
   */
  def singleRepresentedVertex: RepresentedType = {
    if (vertexCollection.size == 1)
      vertexCollection.head
    else
      throw new Error("the collapsed vertex " + this + " does not represent a single vertex.")
  }

  /**
   * @return True if this node represents more than one vertex, otherwise false.
   */
  def isSuperNode: Boolean = vertexCollection.size > 1

  /**
   * Replaces a vertex in the node with another.
   * @param vertexToReplace Vertex to replace.
   * @param replaceWith Vertex to replace the old vertex with.
   * @return A new collapsed node with the vertex replace by the new one.
   */
  def replace(vertexToReplace: RepresentedType, replaceWith: RepresentedType): CollapsedNode[RepresentedType] = {
    val oldSet = vertexCollection
    val newVertexSet = oldSet - vertexToReplace

    if (newVertexSet.size == oldSet.size)
      throw new Error("The vertex " + vertexToReplace + " was not found in set " + vertexCollection)

    if (vertexToReplace == replaceWith)
      throw new IllegalArgumentException("Vertices " + vertexToReplace + " and " + replaceWith + " are equal, and cannot replace each other.")

    new CollapsedNode(newVertexSet + replaceWith)
  }

  /**
   * Checks if this node represents a specific vertex.
   * @param vertex Vertex to check representation status for.
   * @return True if the vertex is represented by this node, otherwise false.
   */
  def contains(vertex: RepresentedType): Boolean = vertexCollection.contains(vertex)

  /**
   * Checks if this node represents a set of vertices of which some make up the
   * specified subset.
   * @param set A set of vertices.
   * @return True if this node represents every vertex (or more) in the specified set.
   */
  def containsSubset(set: Set[RepresentedType]): Boolean = set subsetOf vertexCollection

  override def equals(other: Any): Boolean = other match {
    case c: CollapsedNode[RepresentedType] => vertexCollection == c.vertexCollection
    case _ => false
  }

  override def hashCode: Int = vertexCollection.##
  override def toString: String = "[CollapsedNode: " +  vertexCollection.mkString(",") + "]"

  /**
   * Ensures that the submitted vertices extends Room. Also checks reward capacity.
   */
  private def checkForPointlessRoomsAndRewardCapacity() {
    val vertices = vertexCollection.iterator
    while (vertices.hasNext) {
      val v = vertices.next()
      if (v.isPointless) {
        if (v.pointlessAreaData.isEmpty)
          throw new IllegalArgumentException("The pointless vertex " + v + " doesn't have pointless data set.")
        else
          pointlessVertices += v
      }


      if (v.canHoldAdditionalRewards)
        verticesThatHoldRewards += v

      if (v.canHoldAdditionalActivators)
        verticesThatHoldActivators += v
    }
  }

}
